package top.goldenyear.porpoise.system.account.service;

import com.easy.query.api.proxy.client.EasyEntityQuery;
import com.easy.query.solon.annotation.Db;
import com.jfinal.kit.HashKit;
import com.jfinal.kit.StrKit;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.noear.solon.annotation.Component;
import org.noear.solon.annotation.Inject;
import top.goldenyear.porpoise.common.core.model.Ret;
import top.goldenyear.porpoise.framework.web.core.model.PageResult;
import top.goldenyear.porpoise.framework.web.core.model.query.QPage;
import top.goldenyear.porpoise.framework.web.core.service.BaseService;
import top.goldenyear.porpoise.system.account.model.Account;
import top.goldenyear.porpoise.system.account.model.proxy.AccountProxy;
import top.goldenyear.porpoise.system.permission.model.AccountRole;
import top.goldenyear.porpoise.system.permission.service.AccountRoleService;

/**
 * 管理员账号表 Service 服务层
 *
 * @author airhead
 * @since 2024-10-12
 */
@Component
@Slf4j
public class AccountService extends BaseService<Account, AccountProxy> {
  @Db private EasyEntityQuery easyEntityQuery;
  @Inject private AccountRoleService accountRoleService;

  public AccountService() {
    super(Account.class, AccountProxy.class);
  }

  public Ret createAccount(Account account) {
    Ret ret = validate(account, true);
    if (ret != null) {
      return ret;
    }

    account.setUserName(account.getUserName().trim().toLowerCase());
    passwordSaltAndHash(account);

    boolean save = save(account);
    if (save) {
      accountRoleService.updateAccountRole(account.getId(), account.getUserRoles());
    }

    return Ret.okWithData(true);
  }

  public Ret updateAccount(Account account) {
    Ret ret = validate(account, false);
    if (ret != null) {
      return ret;
    }

    // 密码不为空时，表明密码需要修改
    if (StrKit.notBlank(account.getPassword())) {
      passwordSaltAndHash(account);
    }

    boolean update = updateById(account);
    if (update) {
      accountRoleService.updateAccountRole(account.getId(), account.getUserRoles());
    }

    return Ret.okWithData(true);
  }

  public Account getAccount(Integer id) {
    Account account = getById(id);
    if (account == null) {
      return null;
    }

    account.removeSensitiveInfo();
    fillAccountRoles(account);
    return account;
  }

  public Boolean deleteAccount(Integer id) {
    return deleteById(id);
  }

  /**
   * 默认返回所有可用的用户
   *
   * @param account
   * @return
   */
  public List<Account> listAccount(Account account) {
    if (account == null) {
      account = new Account();
    }

    if (account.getStatus() == null) {
      account.setStatus(1);
    }

    List<Account> list = list(account);
    for (Account record : list) {
      record.removeSensitiveInfo();
    }

    return list;
  }

  public PageResult<Account> paginateAccount(QPage qPage, Account account) {
    PageResult<Account> paginate =
        paginate(
            qPage,
            accountProxy -> {
              accountProxy.userName().like(account.getUserName());
              accountProxy.nickName().like(account.getNickName());
              accountProxy.status().eq(account.getStatus());
            });

    for (Account record : paginate.getRecords()) {
      record.removeSensitiveInfo();

      fillAccountRoles(record);
    }

    return paginate;
  }

  // 密码加盐 hash
  public void passwordSaltAndHash(Account acc) {
    if (StrKit.isBlank(acc.getPassword())) {
      throw new RuntimeException("密码不能为空");
    }

    String salt = HashKit.generateSaltForSha256();
    String pwd = acc.getPassword();
    pwd = HashKit.sha256(salt + pwd);

    acc.setPassword(pwd);
    acc.setSalt(salt);
  }

  @Override
  protected EasyEntityQuery easyEntityQuery() {
    return easyEntityQuery;
  }

  private void fillAccountRoles(Account account) {
    List<AccountRole> accountRoles = accountRoleService.listAccountRoleByAccountId(account.getId());
    List<String> userRoles =
        accountRoles.stream()
            .filter(i -> i.getRole() != null)
            .map(i -> i.getRole().getCode())
            .toList();
    account.setUserRoles(userRoles);
  }

  /**
   * 验证
   *
   * @param create true 表示验证创建 account，否则表示验证更新 account
   */
  private Ret validate(Account account, boolean create) {
    String userName = account.getUserName();
    String password = account.getPassword();
    String nickName = account.getNickName();

    if (StrKit.isBlank(userName)) {
      return Ret.fail("用户名不能为空");
    }

    // 创建
    if (create) {
      if (StrKit.isBlank(password)) {
        return Ret.fail("密码不能为空");
      }
      if (password.trim().length() < 6) {
        return Ret.fail("密码长度至少为 6");
      }
    } else { // 更新
      if (StrKit.notBlank(password) && password.trim().length() < 6) {
        return Ret.fail("密码长度至少为 6");
      }
    }

    if (StrKit.isBlank(nickName)) {
      return Ret.fail("昵称不能为空");
    }

    // 检测是否包含半角或全角空格
    if (userName.contains(" ") || userName.contains("　")) {
      return Ret.fail("用户名不能包含空格");
    }

    // 检测是否包含半角或全角空格
    if (nickName.contains(" ") || nickName.contains("　")) {
      return Ret.fail("昵称不能包含空格");
    }

    // 创建时的 id 为 -1，用于 isUserNameExists、isNickNameExists 方法中使用
    int id = create ? -1 : account.getId();
    if (isUserNameExists(userName, id)) {
      return Ret.fail("用户名已被注册，请换一个用户名");
    }
    if (isNickNameExists(nickName, id)) {
      return Ret.fail("昵称已被注册，请换一个昵称");
    }

    return null;
  }

  /** 用户名是否已被注册 注意：userName 被转换为小写字母后存入数据库 */
  private boolean isUserNameExists(String userName, int selfId) {
    String finalUserName = userName.toLowerCase().trim();
    return get(
            accountProxy -> {
              accountProxy.userName().eq(finalUserName);
              accountProxy.id().ne(selfId);
            })
        != null;
  }

  /**
   * 昵称是否已被注册，昵称不区分大小写，以免存在多个用户昵称看起来一个样的情况
   *
   * <p>mysql 的 where 字句与 order by 子句默认不区分大小写，区分大小写需要在 字段名或字段值前面使用 binary 关键字例如： where nickName =
   * binary "jfinal" 或者 where binary nickName = "jfinal"，前者性能要高
   *
   * <p>为了避免不同的 mysql 配置破坏掉 mysql 的 where 不区分大小写的行为，这里在 sql 中使用 lower(...) 来处理，参数 nickName 也用
   * toLowerCase() 方法来处理，再次确保不区分大小写
   */
  private boolean isNickNameExists(String nickName, int selfId) {
    String finalNickName = nickName.toLowerCase().trim();
    return get(
            accountProxy -> {
              accountProxy.userName().eq(finalNickName);
              accountProxy.id().ne(selfId);
            })
        != null;
  }
}
